package it.softphone.commons.service.security.shiro.realm;


import it.softphone.commons.service.config.ConfigurationServiceFactory;
import it.softphone.commons.util.PasswordManager;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;

import javax.servlet.ServletContext;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.shiro.authc.SimpleAccount;
import org.apache.shiro.authz.SimpleRole;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.util.ThreadContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * @author softphone
 *
 */
public class ShiroWebPropertiesRealm extends TextConfigurationRealm {
    public static final String USERS_SECTION_NAME = "user";
    public static final String ROLES_SECTION_NAME = "role";

    private static transient final Logger log = LoggerFactory.getLogger(ShiroWebPropertiesRealm.class);

    private String resource;
    
	public final String getResource() {
		return resource;
	}

	public final void setResource(String resource) {
		this.resource = resource;
	}

	public ShiroWebPropertiesRealm() {
		super();
		super.setCachingEnabled(true);

	}


	@Override
    protected void onInit() {
		if( null==resource ) throw new IllegalStateException("configuration parameter is null!");

		
		ServletContext context = (ServletContext) ThreadContext.get("javax.servlet.ServletContext");
		
		if( null==context ) throw new IllegalStateException("context is not set!");
		
		Configuration properties = null;
		
		try {
			
			java.util.Map<String,Object> settings = new HashMap<String,Object>(1);
			settings.put( "context.path", context.getRealPath("/")); 
			
			properties = ConfigurationServiceFactory.getConfigurationService(resource, settings);
			
			PasswordManager.decodeProperties(properties);

		} catch (ConfigurationException e) {
			log.error( "error loading configuration from " + resource, e);
			return;
		} catch (IOException e) {
			log.error( "error decoding password from " + resource, e);
			return;
		}

		
        // We override init() instead of onInit() because we _don't_ want any caches to be created
        // (see the superclass init() code).
        // This is an in-memory realm only - no need for an additional cache when we're already
        // as memory-efficient as we can be.

        if (CollectionUtils.isEmpty(this.users) && CollectionUtils.isEmpty(this.roles)) {
        	processPropertiesDefinitions(properties);
        } else {
            log.warn("Users or Roles are already populated.  Resource path property will be ignored.");
        }
    }

		@SuppressWarnings("unchecked")
		private java.util.Map<String,String> toMap( Configuration c) {
			if( c.isEmpty() ) return Collections.emptyMap();
			
			java.util.Map<String,String> result = new java.util.HashMap<String,String>();
			
			java.util.Iterator<String> i = c.getKeys();
			
			while( i.hasNext() ) {
				String k = i.next();
				
				result.put(k, c.getString(k));
			}
			
			return result;
		}
		
		private void processPropertiesDefinitions(Configuration properties) {

	    	Configuration roles = properties.subset(ROLES_SECTION_NAME);
	    	
	        if (!roles.isEmpty()) {
	            log.debug("Discovered the [{}] section.  Processing...", ROLES_SECTION_NAME);
	        
	            processRoleDefinitions( toMap(roles) );
	        }

	        Configuration usersSection = properties.subset(USERS_SECTION_NAME);

	        if (!usersSection.isEmpty()) {
	            log.debug("Discovered the [{}] section.  Processing...", USERS_SECTION_NAME);
	            
	            processUserDefinitions(usersSection);
	        } else {
	            log.info("{} defined, but there is no [{}] section defined.  This realm will not be populated with any " +
	                    "users and it is assumed that they will be populated programatically.  Users must be defined " +
	                    "for this Realm instance to be useful.", getClass().getSimpleName(), USERS_SECTION_NAME);
	        }
	    }
		
	    @SuppressWarnings("unchecked")
		protected void processUserDefinitions(Configuration c) {

	    	java.util.Iterator<String> keys = c.getKeys();
	    	
	        while (keys.hasNext()) {

	        	String username = keys.next();
	        	
	            String[] passwordAndRolesArray = c.getStringArray(username);

	            String password = c.getString( username.concat(".password"));

	            SimpleAccount account = getUser(username);
	            if (account == null) {
	                account = new SimpleAccount(username, password, getName());
	                add(account);
	            }
	            account.setCredentials(password);

	            if (passwordAndRolesArray.length > 0) {
	                for (int i = 0; i < passwordAndRolesArray.length; ++i) {
	                    String rolename = passwordAndRolesArray[i];
	                    account.addRole(rolename);

	                    SimpleRole role = getRole(rolename);
	                    if (role != null) {
	                        account.addObjectPermissions(role.getPermissions());
	                    }
	                }
	            } else {
	                account.setRoles(null);
	            }
	        }
	    }

}
